/*
 * WHICH.C
 *
 * Small minicrt wrapper app that searches the path or other environment
 * variable looking for the first matching file.
 *
 * Copyright (c) 2014-2015 Malcolm J. Smith
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdlib.h>
#include <tchar.h>
#include <minicrt.h>

LPTSTR SearchFor;
LPTSTR SearchVar = _T("PATH");

typedef BOOL (WINAPI * DISABLE_WOW_REDIRECT_FN)(PVOID*);

VOID
WhichUsage(LPTSTR strAppName)
{
    _tprintf(_T("Usage:\n")
             _T(" %s [-p var] file\n")
             _T(" If not specified, var defaults to PATH\n")
             _T(" If PATHEXT not defined, defaults to .COM, .EXE, .BAT and .CMD\n")
             _T(" If file extension not specified and var not specified, searches for files\n")
             _T("  ending in extensions in PATHEXT\n"),
             strAppName);

}

BOOL
WhichParseArgs(int argc, TCHAR* argv[])
{
    int i;

    for (i = 1; i < argc; i++) {
        if (argv[i][0] == '/' || argv[i][0] == '-') {
            BOOL Parsed = FALSE;
            if (_tcsicmp(&argv[i][1], _T("p")) == 0 &&
                argv[i + 1] != NULL) {

                i++;
                SearchVar = argv[i];
                Parsed = TRUE;
            }

            if (!Parsed) {
                return FALSE;
            }
        } else {
            SearchFor = argv[i];
        }
    }

    if (!SearchFor) {
        return FALSE;
    }

    return TRUE;
}

#ifdef UNICODE
#define ENTRYPOINT wmain
#else
#define ENTRYPOINT main
#endif

int __cdecl
ENTRYPOINT (
    int argc,
    TCHAR* argv[]
    )
{
    TCHAR FoundPath[MAX_PATH];
    int Component = 0;
    BOOL ExeSearch = TRUE;
    int i;
    HANDLE hKernel32 = GetModuleHandle(_T("KERNEL32"));
    DISABLE_WOW_REDIRECT_FN DisableWow = (DISABLE_WOW_REDIRECT_FN)GetProcAddress(hKernel32, "Wow64DisableWow64FsRedirection");

    if (!WhichParseArgs(argc, argv)) {
        WhichUsage(argv[0]);
        return EXIT_FAILURE;
    }

    //
    //  When running on WOW64, we don't want file system redirection,
    //  for path evaluation, because 32 bit processes will execute
    //  from 64 bit paths.  For other variables, leave redirection
    //  in place.
    //

    if (DisableWow && _tcsicmp(SearchVar, _T("PATH")) == 0) {
        PVOID DontCare;
        DisableWow(&DontCare);
    }

    //
    //  If there's no extension, search for executable extensions
    //

    if (_tcschr(SearchFor, '.') != NULL) {
        ExeSearch = FALSE;
    }

    if (ExeSearch) {
        TCHAR ExeFile[MAX_PATH];
        TCHAR * PathExtString;
        DWORD PathExtLength;
        BOOLEAN UseDefaultPathExt;
        TCHAR * ThisExt;
        TCHAR * TokCtx;
        const TCHAR DefaultPathExt[] = _T(".com;.exe;.bat;.cmd");
        int LowestExt = -1;
        int LowestExtComponent;

        //
        //  Check if the PathExt environment variable is defined, and if so,
        //  how big it is.  Allocate enough memory to capture it.  If it's
        //  not there, fall back to the default string.
        //

        UseDefaultPathExt = FALSE;
        PathExtLength = GetEnvironmentVariable(_T("PATHEXT"), NULL, 0);
        if (PathExtLength == 0) {
            PathExtLength = _tcslen(DefaultPathExt) + sizeof(TCHAR);
            UseDefaultPathExt = TRUE;
        }

        PathExtString = malloc(PathExtLength * sizeof(TCHAR));
        if (PathExtString == NULL) {
            _tprintf(_T("Out of memory\n"));
            return EXIT_FAILURE;
        }

        if (UseDefaultPathExt) {
            _tcscpy(PathExtString, DefaultPathExt);
        } else {
            GetEnvironmentVariable(_T("PATHEXT"), PathExtString, PathExtLength);
        }

        //
        //  Look for files with each extension in the search path.  Pick the first
        //  one in the path, and if two occur in the same path, pick the first
        //  one in the extension search order
        //

        ThisExt = _tcstok_s(PathExtString, _T(";"), &TokCtx);
        i = 0;
        while (ThisExt != NULL) {

            _stprintf_s(ExeFile, sizeof(ExeFile)/sizeof(ExeFile[0]), _T("%s%s"), SearchFor, ThisExt);
            _tsearchenvex_s(ExeFile, SearchVar, FoundPath, sizeof(FoundPath)/sizeof(FoundPath[0]), &Component);

            if (FoundPath[0] != '\0' &&
                (LowestExt == -1 || Component < LowestExtComponent)) {

                LowestExtComponent = Component;
                LowestExt = i;
            }

            ThisExt = _tcstok_s(NULL, _T(";"), &TokCtx);
            i++;
        }

        //
        //  Now that we've picked an extension, go look it up again so we can
        //  display the result
        //

        if (LowestExt != -1) {
            if (UseDefaultPathExt) {
                _tcscpy(PathExtString, DefaultPathExt);
            } else {
                GetEnvironmentVariable(_T("PATHEXT"), PathExtString, PathExtLength);
            }
            ThisExt = _tcstok_s(PathExtString, _T(";"), &TokCtx);
            i = 0;
            while (ThisExt != NULL && i < LowestExt) {
                ThisExt = _tcstok_s(NULL, _T(";"), &TokCtx);
                i++;
            }
            _stprintf_s(ExeFile, sizeof(ExeFile)/sizeof(ExeFile[0]), _T("%s%s"), SearchFor, ThisExt);
            _tsearchenvex_s(ExeFile, SearchVar, FoundPath, sizeof(FoundPath)/sizeof(FoundPath[0]), &Component);
        }

    } else {

        //
        //  If we have a fully specified extension, just look for it directly
        //

        _tsearchenvex_s(SearchFor, SearchVar, FoundPath, sizeof(FoundPath)/sizeof(FoundPath[0]), &Component);
    }

    //
    //  Tell the user what we found, if anything
    //

    if (*FoundPath) {
        _tprintf(_T("%s\n"), FoundPath);
        return EXIT_SUCCESS;
    } else {
        _tprintf(_T("Not found\n"));
        return EXIT_FAILURE;
    }
}

// vim:sw=4:ts=4:et:
